forked from gxwebsoft/websoft-cms
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
11 KiB
364 lines
11 KiB
<template>
|
|
<div class="container mt-20">
|
|
<Head>
|
|
<Title>{{ $titleRender(`${detail.movie.title}_${detail.title}在线观看`) }}</Title>
|
|
<Meta name="description" :content="detail.movie.summary" />
|
|
</Head>
|
|
|
|
<div class="items-center">
|
|
<span class="mr-10">当前位置</span>
|
|
<el-breadcrumb separator-class="el-icon-arrow-right">
|
|
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
|
<el-breadcrumb-item
|
|
:to="{ path: `/column/${detail.movie.columnValue}/show`, query: { t: detail.movie.genres.split(',')[0] } }"
|
|
>{{ detail.movie.genres.split(',')[0] }}</el-breadcrumb-item
|
|
>
|
|
<el-breadcrumb-item class="hidden-sm-and-down">{{ detail.title }}</el-breadcrumb-item>
|
|
</el-breadcrumb>
|
|
</div>
|
|
<el-row :gutter="40" class="mt-20">
|
|
<el-col :span="18" :xs="24">
|
|
<div id="mse"></div>
|
|
<div>
|
|
<h1 class="mb-10 mt-10 video-detail__title">
|
|
{{ detail.movie.title }} {{ detail.title }}
|
|
<span :class="detail.movie.movieRate?.rateUserCount > 0 ? 'rate' : ''">
|
|
{{ detail.movie.movieRate?.rateUserCount > 0 ? detail.movie.movieRate.rate.toFixed(1) : '暂无评分' }}
|
|
</span>
|
|
</h1>
|
|
<el-form :inline="true">
|
|
<el-form-item label="类型:">{{ detail.movie.genres }}</el-form-item>
|
|
<el-form-item label="地区:">
|
|
<template v-for="item in detail.country">{{ item.name }} </template>
|
|
</el-form-item>
|
|
<el-form-item label="年份:">{{ detail.movie.year }}</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
<div>
|
|
<div class="panel_hd between items-center">
|
|
<div class="panel_hd__left">
|
|
<h3 class="title items-center">
|
|
<el-icon><ElIconVideoCamera /></el-icon>相关视频
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<div v-if="detail.videos.length" class="related_video">
|
|
<ul class="clearfix">
|
|
<li v-for="item in detail.videos" :key="item.id">
|
|
<nuxt-link :to="`/column/${detail.movie.columnValue}/video/${item.id}`">
|
|
<el-image class="img" fit="cover" :src="item.cover || item.video?.poster || ''"></el-image>
|
|
<p :title="item.title">
|
|
<span :class="+item.id === +route.params.id ? 'animate' : ''"
|
|
>{{ +item.id === +route.params.id ? '正在播放: ' : '' }}{{ item.title }}</span
|
|
>
|
|
</p>
|
|
</nuxt-link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="mt-20">
|
|
<div class="panel_hd between items-center">
|
|
<div class="panel_hd__left">
|
|
<h3 class="title items-center">
|
|
<el-icon><ElIconVideoCamera /></el-icon><a href="/">猜你喜欢</a>
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<div class="video-list">
|
|
<el-row :gutter="20">
|
|
<el-col v-for="item in likeRows.rows" :key="item.id" :sm="4" :xs="8">
|
|
<div class="video-list__block">
|
|
<nuxt-link :to="`/column/${item.columnValue}/movie/${item.id}`">
|
|
<el-image class="video-list__block__img" :src="item.poster" fit="cover" />
|
|
</nuxt-link>
|
|
<div class="video-list__detail">
|
|
<h4 class="title text-overflow">{{ item.title }}</h4>
|
|
<p class="text-overflow">
|
|
<template v-for="actor in item.casts">
|
|
{{ actor.actor.name }}
|
|
</template>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6" class="hidden-sm-and-down">
|
|
<div class="qr_code items-center column mb-20">
|
|
<qrcode-vue :value="qrcodeUrl" :size="160" level="H" />
|
|
<p class="mt-10">扫描二维码用手机观看</p>
|
|
</div>
|
|
<!-- 周榜单 -->
|
|
<Ranking title="周榜单" :list="rank.data.weekRank" />
|
|
<!-- 月榜单 -->
|
|
<Ranking title="月榜单" :list="rank.data.mouthRank" />
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
import QrcodeVue from 'qrcode.vue';
|
|
import 'xgplayer/dist/index.min.css';
|
|
import 'xgplayer/es/plugins/danmu/index.css';
|
|
import PresetPlayer from 'xgplayer';
|
|
import { useServerRequest } from '~/composables/useServerRequest';
|
|
import { useClientRequest } from '~/composables/useClientRequest';
|
|
import { MovieVideoInfo } from '~/types/column/video';
|
|
import { UserMovieBase } from '~/types/column/movie';
|
|
import '~/plugins/xgplayer/payTip/index.css';
|
|
import PayTip from '~/plugins/xgplayer/payTip';
|
|
|
|
const token = useToken();
|
|
const loginDialogVisible = useLoginDialogVisible();
|
|
|
|
const route = useRoute();
|
|
const id = route.params.id;
|
|
const qrcodeUrl = ref('');
|
|
// 是否购买了影片
|
|
const isUserBuy = ref(false);
|
|
// 视频支付提示插件
|
|
let payTipInstance: PayTip | null = null;
|
|
// 视频组件
|
|
let player: PresetPlayer | null = null;
|
|
|
|
const { data: detailRes } = await useServerRequest<ResData<MovieVideoInfo | null>>(`/movie/videos/${id}`);
|
|
|
|
if (!detailRes.value?.data) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'Page Not Found',
|
|
fatal: true
|
|
});
|
|
}
|
|
const detail = detailRes.value.data;
|
|
|
|
/** 查询用户是否购买影片 */
|
|
getUserMovie();
|
|
async function getUserMovie() {
|
|
if (token.value) {
|
|
const { data: userBuy } = await useServerRequest<ResData<UserMovieBase>>(`/user-movie`, {
|
|
query: { movieId: detail.movieId }
|
|
});
|
|
isUserBuy.value = !!userBuy.value?.data;
|
|
}
|
|
}
|
|
|
|
watch(token, async () => {
|
|
await getUserMovie();
|
|
payTipInstance?.hide();
|
|
player?.play();
|
|
});
|
|
|
|
onMounted(async () => {
|
|
qrcodeUrl.value = window.location.href;
|
|
|
|
const [Player, Mp4Plugin, Danmu, PayTip] = await Promise.all([
|
|
import('xgplayer'),
|
|
import('xgplayer-mp4'),
|
|
import('xgplayer/es/plugins/danmu'),
|
|
import('~/plugins/xgplayer/payTip')
|
|
]);
|
|
// eslint-disable-next-line new-cap
|
|
player = new Player.default({
|
|
id: 'mse',
|
|
controls: {
|
|
autoHide: false
|
|
},
|
|
autoplay: true,
|
|
volume: 0.3,
|
|
url:
|
|
process.env.NODE_ENV === 'development'
|
|
? detail.videoInfo?.url.replace('http://[::1]:4000', '/server')
|
|
: detail.videoInfo?.url,
|
|
playsinline: true,
|
|
height: '100%',
|
|
width: '100%',
|
|
plugins: [Mp4Plugin.default, Danmu.default, PayTip.default],
|
|
danmu: {
|
|
comments: [
|
|
{
|
|
duration: 15000,
|
|
id: '1',
|
|
start: 3000,
|
|
txt: '好看,精彩!!!',
|
|
// 弹幕自定义样式
|
|
style: {
|
|
color: '#ff9500',
|
|
fontSize: '20px',
|
|
border: 'solid 1px #ff9500',
|
|
borderRadius: '50px',
|
|
padding: '5px 11px',
|
|
backgroundColor: 'rgba(255, 255, 255, 0.1)'
|
|
}
|
|
}
|
|
],
|
|
area: {
|
|
start: 0,
|
|
end: 1
|
|
}
|
|
},
|
|
payTip: {
|
|
tip: `此为付费视频,支付${detail.movie.paymentAmount}金币继续观看?`,
|
|
lookTime: detail.movie.freeDuration * 60,
|
|
arriveTime() {
|
|
if (isUserBuy.value) return;
|
|
// 影片设置了需要购买才能观看并且是正片
|
|
if (+detail.movie.isPay === 1 && +detail.typeId === 1) {
|
|
player?.pause();
|
|
payTipInstance?.show('flex');
|
|
}
|
|
},
|
|
clickButton() {
|
|
if (!token.value) {
|
|
loginDialogVisible.value = true;
|
|
} else {
|
|
player && buyMovie(player);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
payTipInstance = player.getPlugin('payTip');
|
|
});
|
|
|
|
/** 购买影视 */
|
|
function buyMovie(player: PresetPlayer) {
|
|
ElMessageBox.confirm(`确定要支付${detail.movie.paymentAmount}金币购买此影片吗?`, '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(async () => {
|
|
const { code, msg } = await useClientRequest<ResData<UserMovieBase>>(`/user-movie`, {
|
|
method: 'post',
|
|
body: { movieId: detail.movieId }
|
|
});
|
|
if (code === 200) {
|
|
isUserBuy.value = true;
|
|
player.play();
|
|
payTipInstance?.hide();
|
|
ElMessage({
|
|
type: 'success',
|
|
message: '购买成功'
|
|
});
|
|
} else {
|
|
ElMessage({
|
|
type: 'error',
|
|
message: msg
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/** 获取猜你喜欢、周榜单、月榜单数据 */
|
|
const [{ data: likeRows }, { data: rank }] = await Promise.all([
|
|
useServerRequest<ResPage<MovieItem[]>>(`/movie/list`, {
|
|
query: {
|
|
genres: detail.movie.genres.split(',')[0],
|
|
pageNum: 1,
|
|
pageSize: 18
|
|
}
|
|
}),
|
|
useServerRequest<ResData<LeaderboardItem>>('/movie/leaderboard', {
|
|
query: {
|
|
columnValue: detail.movie.columnValue,
|
|
pageNum: 1,
|
|
pageSize: 20
|
|
}
|
|
})
|
|
]);
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
#mse {
|
|
width: 100%;
|
|
height: 500px !important;
|
|
background: #000;
|
|
}
|
|
@media (max-width: 768px) {
|
|
#mse {
|
|
height: 300px !important;
|
|
}
|
|
}
|
|
.title {
|
|
.el-icon {
|
|
margin-right: 15px;
|
|
}
|
|
}
|
|
.related_video {
|
|
overflow-x: auto;
|
|
width: 100%;
|
|
overflow-y: hidden;
|
|
.img {
|
|
width: 160px;
|
|
height: 100px;
|
|
}
|
|
ul {
|
|
white-space: nowrap;
|
|
li {
|
|
display: inline-block;
|
|
margin-right: 20px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
p {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
color: #ffffff;
|
|
height: 36px;
|
|
line-height: 36px;
|
|
padding-left: 10px;
|
|
box-sizing: border-box;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.animate {
|
|
padding-left: 20px;
|
|
white-space: nowrap;
|
|
display: inline-block;
|
|
animation: 15s wordsLoop linear infinite normal;
|
|
color: #ffffff;
|
|
}
|
|
|
|
@keyframes wordsLoop {
|
|
0% {
|
|
transform: translateX(0px);
|
|
-webkit-transform: translateX(0px);
|
|
}
|
|
100% {
|
|
transform: translateX(-100%);
|
|
-webkit-transform: translateX(-100%);
|
|
}
|
|
}
|
|
|
|
@-webkit-keyframes wordsLoop {
|
|
0% {
|
|
transform: translateX(0px);
|
|
-webkit-transform: translateX(0px);
|
|
}
|
|
100% {
|
|
transform: translateX(-100%);
|
|
-webkit-transform: translateX(-100%);
|
|
}
|
|
}
|
|
|
|
.video-detail__title {
|
|
line-height: 1.2;
|
|
|
|
span {
|
|
color: $light-gray;
|
|
font-size: 14px;
|
|
&.rate {
|
|
font-size: 24px;
|
|
color: #06d706;
|
|
}
|
|
}
|
|
}
|
|
</style>
|